/*
    bio_alphabet.h: Molecular biology alphabets.
    Copyright (C) 2010, 2012 Daniel Gutson and Hugo Arregui, FuDePAN

    This file is part of Biopp.

    Biopp is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Biopp is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Biopp.  If not, see <http://www.gnu.org/licenses/>.

    NOTE: This file is in prototype stage, and is under active development.
*/

#ifndef BIO_ALPHABET_H
#define BIO_ALPHABET_H

#include <cctype>
#include <mili/mili.h>
#include "exceptions.h"

namespace biopp
{

typedef unsigned int AlphabetUnderlyingType;

template <template<class> class Alpha1, template<class> class Alpha2>
struct CombinationValid
{
    enum { Valid = true };
};

template <template<class> class Alpha>
struct CombinationValid<Alpha, Alpha>
{
    enum { Valid = false }; // cannot repeat same alphabet.
};

template <class Alpha>
struct IsAlphaValid
{
    enum { Valid = false };
};

template <size_t Min>
struct CheckRange
{
    static bool inRange(size_t value)
    {
        return value >= Min;
    }
};

template <>
struct CheckRange<0u>
{
    static bool inRange(size_t /*value*/)
    {
        return true;
    }
};


struct Base_Alphabet
{
    AlphabetUnderlyingType value;
protected:
    enum { Length = 0 };

    void operator=(char)
    {
        throw MethodNotImplemented();
    }

    template <class Alpha, unsigned int DATA_SIZE>
    char as_char(const char data[DATA_SIZE]) const
    {
        typedef typename Alpha::NextAlphabet Next;

        if (CheckRange<Next::Length>::inRange(value))       // Next::Length is equal to the first Alpha element
        {
            const unsigned int index = value - Next::Length;
            if (index < DATA_SIZE)
            {
                return data[index];
            }
            else
            {
                return this->as_char();
            }
        }
        else
            return static_cast<const Next*>(this)->as_char();   // upcast
    }

    char as_char() const
    {
        throw MethodNotImplemented();
    }

    template <class Alpha, unsigned int DATA_SIZE>
    void complement(const char data[DATA_SIZE])
    {
        typedef typename Alpha::NextAlphabet Next;
        if (CheckRange<Next::Length>::inRange(value))       // Next::Length is equal to the first Alpha element
        {
            const unsigned int index = value - Next::Length;
            if (index < DATA_SIZE)
            {
                value = data[index];
            }
            else
            {
                this->complement();
            }
        }
        else
            static_cast<Next*>(this)->complement(); // upcast
    }

    void complement() {}
};

template <>
struct IsAlphaValid<Base_Alphabet>
{
    enum { Valid = true };
    typedef int dummy;
};

template <template<class> class Alpha>
struct IsAlphaValid<Alpha<Base_Alphabet> >
{
    enum { Valid = true };
    typedef int dummy;
};

declare_static_assert(InvalidCombination);

template <template <class> class Alpha1, template<class> class Alpha2, class N>
struct IsAlphaValid<Alpha1<Alpha2<N> > >
{
    enum { Valid = CombinationValid<Alpha1, Alpha2>::Valid && IsAlphaValid<Alpha1<N> >::Valid};
    template_compile_assert(Valid, InvalidCombination);
    typedef int dummy;
};

#define INVALIDATE1(A1, A2)     \
template<>                      \
struct CombinationValid<A1, A2> \
{                               \
    enum { Valid = false };     \
}

#define INVALIDATE(A1, A2)      \
INVALIDATE1(A1, A2);            \
INVALIDATE1(A2, A1)

#define ALPHA_PROLOG(Alpha, len)                         \
    typedef Next NextAlphabet;                           \
    typedef typename IsAlphaValid<Alpha>::dummy _dummy;  \
    enum { Alpha##_length = len };                       \
    enum { Length = Next::Length + len };                \
                                                         \
    Alpha()                                              \
    {}                                                   \
                                                         \
    template <class T>                                   \
    Alpha(T n)                                           \
    {                                                    \
        this->operator=(n);                              \
    }                                                    \
                                                         \
    bool operator<(const Alpha& other) const             \
    {                                                    \
        return this->value < other.value;                \
    }                                                    \
                                                         \
    bool operator>(const Alpha& other) const             \
    {                                                    \
        return this->value > other.value;                \
    }                                                    \
                                                         \
    bool operator==(const Alpha& other) const            \
    {                                                    \
        return this->value == other.value;               \
    }                                                    \
                                                         \
    bool operator!=(const Alpha& other) const            \
    {                                                    \
        return this->value != other.value;               \
    }

template <class Next = Base_Alphabet>
struct Deletion_Alphabet : Next
{
    ALPHA_PROLOG(Deletion_Alphabet, 1)

    enum Alpha
    {
        Deletion = Next::Length
    };

    void operator=(char v)
    {
        if (v == '-' || v == '.')
            this->value = Deletion;
        else
            Next::operator=(v);
    }

    void operator=(const Deletion_Alphabet<Next>& other)
    {
        this->value = other.value;
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    using Next::operator=;

    char as_char() const
    {
        if (this->value == Deletion)
            return '-';
        else
            return Next::as_char();
    }

    using Next::complement;
};

template <class Next> struct AUCG_Alphabet;
template <class Next> struct ATCG_Alphabet;
template <class Next> struct AXCG_Alphabet;

template <class Next = Base_Alphabet>
struct AUCG_Alphabet : Next
{
    ALPHA_PROLOG(AUCG_Alphabet, 4)

    enum Alpha
    {
        A = Next::Length,
        U,
        C,
        G
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'A':
                this->value = A;
                break;
            case 'U':
                this->value = U;
                break;
            case 'C':
                this->value = C;
                break;
            case 'G':
                this->value = G;
                break;
            default:
                Next::operator=(v);
        }
    }

    void operator=(const AUCG_Alphabet<Next>& other)
    {
        this->value = other.value;
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    template <class N>
    inline void operator=(const ATCG_Alphabet<N>& atcg);

    template <class N>
    inline void operator=(const AXCG_Alphabet<N>& axcg);

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { 'A', 'U', 'C', 'G' };
        return Base_Alphabet::as_char<AUCG_Alphabet, AUCG_Alphabet_length>(data);
    }

    void complement()
    {
        static const char data[] = { U, A, G, C };
        Base_Alphabet::complement<AUCG_Alphabet, AUCG_Alphabet_length>(data);
    }
};

template <class Next = Base_Alphabet>
struct ATCG_Alphabet : Next
{
    ALPHA_PROLOG(ATCG_Alphabet, 4)

    enum Alpha
    {
        A = Next::Length,
        T,
        C,
        G
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'A':
                this->value = A;
                break;
            case 'T':
                this->value = T;
                break;
            case 'C':
                this->value = C;
                break;
            case 'G':
                this->value = G;
                break;
            default:
                Next::operator=(v);
        }
    }

    void operator=(const ATCG_Alphabet<Next>& other)
    {
        this->value = other.value;
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    template <class N>
    inline void operator=(const AUCG_Alphabet<N>& aucg);

    template <class N>
    inline void operator=(const AXCG_Alphabet<N>& axcg);

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { 'A', 'T', 'C', 'G' };
        return Base_Alphabet::as_char<ATCG_Alphabet, ATCG_Alphabet_length>(data);
    }

    void complement()
    {
        static const char data[] = { T, A, G, C };
        Base_Alphabet::complement<ATCG_Alphabet, ATCG_Alphabet_length>(data);
    }

};

template <class Next = Base_Alphabet>
struct AXCG_Alphabet : Next
{
    ALPHA_PROLOG(AXCG_Alphabet, 4)

    enum Alpha
    {
        A = Next::Length,
        U,
        C,
        G
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'A':
                this->value = A;
                break;
            case 'U':
                this->value = U;
                break;
            case 'T':
                this->value = U;
                break;
            case 'C':
                this->value = C;
                break;
            case 'G':
                this->value = G;
                break;
            default :
                Next::operator=(v);
        }
    }

    void operator=(const AXCG_Alphabet<Next>& other)
    {
        this->value = other.value;
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    template <class N>
    inline void operator=(const ATCG_Alphabet<N>& atcg);

    template <class N>
    inline void operator=(const AUCG_Alphabet<N>& aucg);

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { 'A', 'U', 'C', 'G' };
        return Base_Alphabet::as_char<AXCG_Alphabet, AXCG_Alphabet_length>(data);
    }

    void complement()
    {
        static const char data[] = { U, A, G, C };
        Base_Alphabet::complement<AXCG_Alphabet, AXCG_Alphabet_length>(data);
    }
};

template <class Next = Base_Alphabet>
struct Pseudo_Alphabet: Next
{
    ALPHA_PROLOG(Pseudo_Alphabet, 12)

    enum Alpha
    {
        R = Next::Length,
        Y,
        K,
        M,
        S,
        W,
        B,
        D,
        H,
        V,
        N,
        STOP_PN
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'R':
                this->value = R;
                break;
            case 'Y':
                this->value = Y;
                break;
            case 'K':
                this->value = K;
                break;
            case 'M':
                this->value = M;
                break;
            case 'S':
                this->value = S;
                break;
            case 'W':
                this->value = W;
                break;
            case 'B':
                this->value = B;
                break;
            case 'D':
                this->value = D;
                break;
            case 'H':
                this->value = H;
                break;
            case 'V':
                this->value = V;
                break;
            case 'N':
                this->value = N;
                break;
            default :
                Next::operator=(v);
        }
    }

    void operator=(const Pseudo_Alphabet<Next>& other)
    {
        this->value = other.value;
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    template <class N>
    inline void operator=(const ATCG_Alphabet<N>& atcg);

    template <class N>
    inline void operator=(const AUCG_Alphabet<N>& aucg);

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { 'R', 'Y', 'K', 'M', 'S', 'W', 'B', 'D', 'H', 'V', 'N' };
        return Base_Alphabet::as_char<Pseudo_Alphabet, Pseudo_Alphabet_length>(data);
    }

    void complement()
    {
        static const char data[] = { Y, R, M, K, S, W, V, H, D, B, N, STOP_PN };
        Base_Alphabet::complement<Pseudo_Alphabet, Pseudo_Alphabet_length>(data);
    }
};

template <class Next = Base_Alphabet>
struct Aminoacid_Standard_Alphabet: Next
{
    ALPHA_PROLOG(Aminoacid_Standard_Alphabet, 20)

    enum Alpha
    {
        A = Next::Length,
        C,
        D,
        E,
        F,
        G,
        H,
        I,
        K,
        L,
        M,
        N,
        P,
        Q,
        R,
        S,
        T,
        V,
        W,
        Y
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'A':
                this->value = A;
                break;
            case 'C':
                this->value = C;
                break;
            case 'D':
                this->value = D;
                break;
            case 'E':
                this->value = E;
                break;
            case 'F':
                this->value = F;
                break;
            case 'G':
                this->value = G;
                break;
            case 'H':
                this->value = H;
                break;
            case 'I':
                this->value = I;
                break;
            case 'K':
                this->value = K;
                break;
            case 'L':
                this->value = L;
                break;
            case 'M':
                this->value = M;
                break;
            case 'N':
                this->value = N;
                break;
            case 'P':
                this->value = P;
                break;
            case 'Q':
                this->value = Q;
                break;
            case 'R':
                this->value = R;
                break;
            case 'S':
                this->value = S;
                break;
            case 'T':
                this->value = T;
                break;
            case 'V':
                this->value = V;
                break;
            case 'W':
                this->value = W;
                break;
            case 'Y':
                this->value = Y;
                break;
            default:
                Next::operator=(v);
        }
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { 'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K',
                                     'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'Y'
                                   };
        return Base_Alphabet::as_char<Aminoacid_Standard_Alphabet, Aminoacid_Standard_Alphabet_length>(data);
    }
    using Next::complement;
};



template <class Next = Base_Alphabet>
struct Aminoacid_StandardEx_Alphabet: Aminoacid_Standard_Alphabet<Next>
{
    ALPHA_PROLOG(Aminoacid_StandardEx_Alphabet, 2 + Aminoacid_Standard_Alphabet<Next>::Aminoacid_Standard_Alphabet_length)

    enum Alpha
    {
        O = Aminoacid_Standard_Alphabet<Next>::Length,
        U
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'O':
                this->value = O;
                break;
            case 'U':
                this->value = U;
                break;
            default:
                Aminoacid_Standard_Alphabet<Next>::operator=(v);
        }
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    using Aminoacid_Standard_Alphabet<Next>::operator=;

    char as_char() const
    {
        static const char data[] = {'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K',
                                    'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 
                                    'W', 'Y', 'O', 'U' };
        return Base_Alphabet::as_char<Aminoacid_StandardEx_Alphabet, Aminoacid_StandardEx_Alphabet_length>(data);
    }
    using Aminoacid_Standard_Alphabet<Next>::complement;
};

template <class Next = Base_Alphabet>
struct PseudoAminoacid_Alphabet: Next
{
    ALPHA_PROLOG(PseudoAminoacid_Alphabet, 4)

    enum Alpha
    {
        B = Next::Length,
        J,
        X,
        Z
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case 'B':
                this->value = B;
                break;
            case 'J':
                this->value = J;
                break;
            case 'X':
                this->value = X;
                break;
            case 'Z':
                this->value = Z;
                break;
            default:
                Next::operator=(v);
        }
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { 'B', 'J', 'X', 'Z' };
        return Base_Alphabet::as_char<PseudoAminoacid_Alphabet, PseudoAminoacid_Alphabet_length>(data);
    }
    using Next::complement;
};

template <class Next = Base_Alphabet>
struct StopAminoacid_Alphabet: Next
{
    ALPHA_PROLOG(StopAminoacid_Alphabet, 1)

    enum Alpha
    {
        STOP_CODON = Next::Length
    };

    void operator=(char v)
    {
        switch (std::toupper(v))
        {
            case '*':
                this->value = STOP_CODON;
                break;
            default:
                Next::operator=(v);
        }
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    using Next::operator=;

    char as_char() const
    {
        static const char data[] = { '*' };
        return Base_Alphabet::as_char<StopAminoacid_Alphabet, StopAminoacid_Alphabet_length>(data);
    }
    using Next::complement;
};


typedef StopAminoacid_Alphabet<PseudoAminoacid_Alphabet<Aminoacid_StandardEx_Alphabet<Base_Alphabet> > > Full_Aminoacid_Alphabet;

template <class Next = Base_Alphabet>
struct Aminoacid_Alphabet: StopAminoacid_Alphabet<PseudoAminoacid_Alphabet<Aminoacid_StandardEx_Alphabet<Next> > >
{
    typedef StopAminoacid_Alphabet<PseudoAminoacid_Alphabet<Aminoacid_StandardEx_Alphabet<Next> > > Full_Aminoacid_Alphabet;
    ALPHA_PROLOG(Aminoacid_Alphabet, Full_Aminoacid_Alphabet::StopAminoacid_Alphabet_length)

    enum Alpha
    {
        NILL = Next::Length
    };


    void operator=(char v)
    {
        Full_Aminoacid_Alphabet::operator=(v);
    }

    void operator=(Alpha a)
    {
        this->value = a;
    }

    using Full_Aminoacid_Alphabet::operator=;

    using Full_Aminoacid_Alphabet::as_char;

    using Full_Aminoacid_Alphabet::complement;
};

#define BIO_ALPHABET_INLINE_H
#include "bio_alphabet_inline.h"
#undef BIO_ALPHABET_INLINE_H

INVALIDATE(ATCG_Alphabet, AUCG_Alphabet);
INVALIDATE(ATCG_Alphabet, AXCG_Alphabet);
INVALIDATE(ATCG_Alphabet, Aminoacid_Alphabet);
INVALIDATE(ATCG_Alphabet, Aminoacid_Standard_Alphabet);
INVALIDATE(ATCG_Alphabet, Aminoacid_StandardEx_Alphabet);
INVALIDATE(ATCG_Alphabet, PseudoAminoacid_Alphabet);
INVALIDATE(ATCG_Alphabet, StopAminoacid_Alphabet);

INVALIDATE(AUCG_Alphabet, AXCG_Alphabet);
INVALIDATE(AUCG_Alphabet, Aminoacid_Alphabet);
INVALIDATE(AUCG_Alphabet, Aminoacid_Standard_Alphabet);
INVALIDATE(AUCG_Alphabet, Aminoacid_StandardEx_Alphabet);
INVALIDATE(AUCG_Alphabet, PseudoAminoacid_Alphabet);
INVALIDATE(AUCG_Alphabet, StopAminoacid_Alphabet);

INVALIDATE(AXCG_Alphabet, Aminoacid_Alphabet);
INVALIDATE(AXCG_Alphabet, Aminoacid_Standard_Alphabet);
INVALIDATE(AXCG_Alphabet, Aminoacid_StandardEx_Alphabet);
INVALIDATE(AXCG_Alphabet, PseudoAminoacid_Alphabet);
INVALIDATE(AXCG_Alphabet, StopAminoacid_Alphabet);

INVALIDATE(Pseudo_Alphabet, Aminoacid_Alphabet);
INVALIDATE(Pseudo_Alphabet, Aminoacid_Standard_Alphabet);
INVALIDATE(Pseudo_Alphabet, Aminoacid_StandardEx_Alphabet);
INVALIDATE(Pseudo_Alphabet, PseudoAminoacid_Alphabet);
INVALIDATE(Pseudo_Alphabet, StopAminoacid_Alphabet);

INVALIDATE(Aminoacid_Alphabet, Aminoacid_Standard_Alphabet);
INVALIDATE(Aminoacid_Alphabet, Aminoacid_StandardEx_Alphabet);
INVALIDATE(Aminoacid_Alphabet, PseudoAminoacid_Alphabet);
INVALIDATE(Aminoacid_Alphabet, StopAminoacid_Alphabet);

INVALIDATE(Aminoacid_Standard_Alphabet, Aminoacid_StandardEx_Alphabet);









}
#endif
